package com.hsy.swu.other.game2048

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.BounceInterpolator
import android.view.animation.DecelerateInterpolator
import androidx.core.view.contains
import kotlin.math.abs
import kotlin.random.Random

class Game2048View(context: Context, attrs: AttributeSet?) : ViewGroup(context, attrs) {
    private val colorBackground = Color.parseColor("#80D2B48C")
    private val mColorMap = hashMapOf(
        2 to Color.parseColor("#EFEFEF"),
        4 to Color.parseColor("#E0E0E0"),
        8 to Color.parseColor("#F5BCBC"),
        16 to Color.parseColor("#F68989"),
        32 to Color.parseColor("#F25B5B"),
        64 to Color.parseColor("#EF3030"),
        128 to Color.parseColor("#FEC557"),
        256 to Color.parseColor("#FCAD2F"),
        512 to Color.parseColor("#F89A17"),
        1024 to Color.parseColor("#F77E0A"),
        2048 to Color.parseColor("#F76602"),
        4096 to Color.parseColor("#F75100")
    )

    private var mSize = 0 // 长宽尺寸
    private var mGap = 0 // 格子间隙
    private var mBlockSize = 0 // 格子尺寸
    private val mRcCount = 4 // 行列数
    private var mL = 0 // 背景 left
    private var mT = 0 // 背景 top
    private var mR = 0 // 背景 right
    private var mB = 0 // 背景 bottom
    private val mCornerRadius = 50f // 圆角弧度

    private var firstLeft = 0 // 第一个 block left
    private var firstTop = 0 // 第一个 block top

    private var mBlockArray = Array(mRcCount) {
        Array<Game2048Block?>(mRcCount) {
            null
        }
    }

    fun getBlockArray() = mBlockArray

    var mScore = 0
    private var mScoreListener: ((Int) -> Unit)? = null
    private var mGameOverListener: ((Int) -> Unit)? = null

    private var mRestoreOnLayoutListener: (() -> Unit)? = null

    private var mTypeFace: Typeface? = null
    fun setTypeFace(typeface: Typeface?) {
        mTypeFace = typeface
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        Log.e("hsy1", "onMeasure")
        mSize = minOf(measuredWidth, measuredHeight)
        mGap = mSize / ((mRcCount + 1) * 10)
        mBlockSize = (mSize - mGap * (mRcCount + 1)) / mRcCount

        mL = (measuredWidth - mSize) / 2
        mT = (measuredHeight - mSize) / 2
        mR = (measuredWidth + mSize) / 2
        mB = (measuredHeight + mSize) / 2

        firstLeft = mL + mGap
        firstTop = mT + mGap
    }

    private val mBackgroundView = Game2048Background(context) // 背景 View

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        Log.e("hsy1", "onLayout")

        if (!contains(mBackgroundView)) {
            addView(mBackgroundView)
            mBackgroundView.layout(mL, mT, mR, mB)
        }

        mRestoreOnLayoutListener?.apply {
            invoke()
            mRestoreOnLayoutListener = null
        }

        Log.e("hsy1", "--------------------------------------------------")
        mBlockArray.forEach { row ->
            val toLogStringBuilder = StringBuilder()
            repeat(mRcCount) {
                toLogStringBuilder.append("${row[it]?.value ?: "_"}\t\t")
            }
            Log.v("hsy1", toLogStringBuilder.toString())
        }
        Log.e("hsy1", "--------------------------------------------------")
    }

    private var startX = 0f
    private var startY = 0f
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (isSlidingBlockCount != 0) return true// 如果有 block正在滑动就返回，否则由于 translationXBy和 translationYBy方法，可能错位
        requestDisallowInterceptTouchEvent(true)

        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                startX = event.x
                startY = event.y
            }

            MotionEvent.ACTION_UP -> {
                if (abs(event.x - startX) > abs(event.y - startY)) {
                    if (abs(event.x - startX) > 100) {
                        // 横向
                        if (event.x - startX > 0) {
                            slideToRightCheck()
                        } else {
                            slideToLeftCheck()
                        }
                    }
                }

                if (abs(event.x - startX) < abs(event.y - startY)) {
                    if (abs(event.y - startY) > 100) {
                        // 纵向
                        if (event.y - startY > 0) {
                            slideToBottomCheck()
                        } else {
                            slideToTopCheck()
                        }
                    }
                }

                startX = 0f
                startY = 0f
            }
        }
        return true
    }

    fun initGame() {
        mScore = 0
        for (row in 0..mRcCount - 1) {
            for (column in 0..mRcCount - 1) {
                removeView(mBlockArray[row][column])
                mBlockArray[row][column] = null
            }
        }
        val randomPair = getRandomRowColumn()
        addBlock(2, randomPair.first, randomPair.second)

        /*addBlock(2, 0, 0)
        addBlock(2, 0, 1)
        addBlock(8, 0, 2)
        addBlock(4, 0, 3)
        addBlock(8, 1, 0)
        addBlock(16, 1, 1)
        addBlock(32, 1, 2)
        addBlock(8, 1, 3)
        addBlock(128, 2, 0)
        addBlock(256, 2, 1)
        addBlock(64, 2, 2)
        addBlock(32, 2, 3)
        addBlock(256, 3, 0)
        addBlock(512, 3, 1)
        addBlock(1024, 3, 2)
        addBlock(128, 3, 3)*/
    }

    fun restoreGame(array: Array<Array<Game2048View.Game2048Block?>>?) {
        array ?: return
        mBlockArray = array
        mBlockArray.forEach {
            it.forEach { block ->
                if (block != null) {
                    addBlock(block.value, block.row, block.column)
                }
            }
        }
    }

    fun setRestoreOnLayoutListener(listener: () -> Unit) {
        mRestoreOnLayoutListener = listener
    }

    private fun addBlock(
        value: Int,
        row: Int,
        column: Int,
        onAnimEnd: () -> Unit = {}
    ) {
        val block = Game2048Block(context, row, column)
        block.value = value
        mBlockArray[row][column] = block
        mScore += value
        mScoreListener?.invoke(mScore)
        addView(block)
        block.layout(
            firstLeft + block.column * (mBlockSize + mGap),
            firstTop + block.row * (mBlockSize + mGap),
            firstLeft + block.column * (mBlockSize + mGap) + mBlockSize,
            firstTop + block.row * (mBlockSize + mGap) + mBlockSize
        )
        block.animShow(onAnimEnd)
    }

    private fun slideToRightCheck() {
        repeat(mRcCount) { row ->
            var lastBlock: Game2048Block? = null
            var lastIndex = mRcCount - 1
            for (column in mRcCount - 1 downTo 0) {
                if (mBlockArray[row][column] != null) {
                    if (lastBlock == null) {
                        lastBlock = mBlockArray[row][column]!!
                        slideBlock(lastBlock, row, lastIndex)
                        mBlockArray[row][column] = null
                        mBlockArray[row][lastIndex] = lastBlock
                    } else {
                        val tempBlock = mBlockArray[row][column]!!
                        mBlockArray[row][column] = null
                        val tempLastIndex = lastIndex
                        lastIndex -= 1
                        if (tempBlock.value == lastBlock.value) {
                            lastBlock = null
                            slideBlock(tempBlock, row, tempLastIndex) {
                                removeView(tempBlock)
                                mBlockArray[row][tempLastIndex]!!.apply {
                                    value *= 2
                                    invalidate()
                                    animShow()
                                }
                            }
                        } else {
                            slideBlock(tempBlock, row, lastIndex)
                            mBlockArray[row][lastIndex] = tempBlock
                            lastBlock = tempBlock
                        }
                    }
                }
            }
        }
    }

    private fun slideToLeftCheck() {
        repeat(mRcCount) { row ->
            var lastBlock: Game2048Block? = null
            var lastIndex = 0
            for (column in 0..mRcCount - 1) {
                if (mBlockArray[row][column] != null) {
                    if (lastBlock == null) {
                        lastBlock = mBlockArray[row][column]!!
                        slideBlock(lastBlock, row, lastIndex)
                        mBlockArray[row][column] = null
                        mBlockArray[row][lastIndex] = lastBlock
                    } else {
                        val tempBlock = mBlockArray[row][column]!!
                        mBlockArray[row][column] = null
                        val tempLastIndex = lastIndex
                        lastIndex += 1
                        if (tempBlock.value == lastBlock.value) {
                            lastBlock = null
                            slideBlock(tempBlock, row, tempLastIndex) {
                                removeView(tempBlock)
                                mBlockArray[row][tempLastIndex]!!.apply {
                                    value *= 2
                                    invalidate()
                                    animShow()
                                }
                            }
                        } else {
                            slideBlock(tempBlock, row, lastIndex)
                            mBlockArray[row][lastIndex] = tempBlock
                            lastBlock = tempBlock
                        }
                    }
                }
            }
        }
    }

    private fun slideToBottomCheck() {
        repeat(mRcCount) { column ->
            var lastBlock: Game2048Block? = null
            var lastIndex = mRcCount - 1
            for (row in mRcCount - 1 downTo 0) {
                if (mBlockArray[row][column] != null) {
                    if (lastBlock == null) {
                        lastBlock = mBlockArray[row][column]!!
                        slideBlock(lastBlock, lastIndex, column)
                        mBlockArray[row][column] = null
                        mBlockArray[lastIndex][column] = lastBlock
                    } else {
                        val tempBlock = mBlockArray[row][column]!!
                        mBlockArray[row][column] = null
                        val tempLastIndex = lastIndex
                        lastIndex -= 1
                        if (tempBlock.value == lastBlock.value) {
                            lastBlock = null
                            slideBlock(tempBlock, tempLastIndex, column) {
                                removeView(tempBlock)
                                mBlockArray[tempLastIndex][column]!!.apply {
                                    value *= 2
                                    invalidate()
                                    animShow()
                                }
                            }
                        } else {
                            slideBlock(tempBlock, lastIndex, column)
                            mBlockArray[lastIndex][column] = tempBlock
                            lastBlock = tempBlock
                        }
                    }
                }
            }
        }
    }

    private fun slideToTopCheck() {
        repeat(mRcCount) { column ->
            var lastBlock: Game2048Block? = null
            var lastIndex = 0
            for (row in 0..mRcCount - 1) {
                if (mBlockArray[row][column] != null) {
                    if (lastBlock == null) {
                        lastBlock = mBlockArray[row][column]!!
                        slideBlock(lastBlock, lastIndex, column)
                        mBlockArray[row][column] = null
                        mBlockArray[lastIndex][column] = lastBlock
                    } else {
                        val tempBlock = mBlockArray[row][column]!!
                        mBlockArray[row][column] = null
                        val tempLastIndex = lastIndex
                        lastIndex += 1
                        if (tempBlock.value == lastBlock.value) {
                            lastBlock = null
                            slideBlock(tempBlock, tempLastIndex, column) {
                                removeView(tempBlock)
                                mBlockArray[tempLastIndex][column]!!.apply {
                                    value *= 2
                                    invalidate()
                                    animShow()
                                }
                            }
                        } else {
                            slideBlock(tempBlock, lastIndex, column)
                            mBlockArray[lastIndex][column] = tempBlock
                            lastBlock = tempBlock
                        }
                    }
                }
            }
        }
    }

    private var isSlidingBlockCount = 0
    private var isSlided = false // 一次触屏后是否有滑动
    private fun slideBlock(
        block: Game2048Block,
        toRow: Int,
        toColumn: Int,
        onAnimEnd: () -> Unit = {}
    ) {
        isSlidingBlockCount++
        val tX = (mBlockSize + mGap) * (toColumn - block.column).toFloat()
        val tY = (mBlockSize + mGap) * (toRow - block.row).toFloat()
        if (tX != 0f && tY != 0f) throw IllegalStateException("不能同时横向纵向移动")
        if (tX != 0f || tY != 0f) isSlided = true
        block.row = toRow
        block.column = toColumn
        block.animate()
            .translationXBy(tX)
            .translationYBy(tY)
            .setDuration(250)
            .setInterpolator(BounceInterpolator())
            .withEndAction {
                isSlidingBlockCount--
                if (isSlidingBlockCount == 0) {
                    if (isSlided) {
                        isSlided = false
                        val randomPair = getRandomRowColumn()
                        addBlock(2, randomPair.first, randomPair.second) {
                            if (isGameOver()) {
                                mGameOverListener?.invoke(mScore)
                                Log.e("hsy1", "游戏结束")
                            }
                        }
                    }
                }
                onAnimEnd()
            }
            .start()
    }

    private fun isGameOver(): Boolean {
        for (row in 0..mRcCount - 1) {
            for (column in 0..mRcCount - 1) {
                val currentBlock = mBlockArray[row][column] ?: return false
                if (row != mRcCount - 1) {
                    if (
                        currentBlock.value == (mBlockArray[row + 1][column]?.value ?: return false)
                    ) {
                        return false
                    }
                }
                if (column != mRcCount - 1) {
                    if (
                        currentBlock.value == (mBlockArray[row][column + 1]?.value ?: return false)
                    ) {
                        return false
                    }
                }
            }
        }
        return true
    }

    private fun getRandomRowColumn(): Pair<Int, Int> {
        // 进入前必须判断是否满了，不然会死循环
        val startTime = System.currentTimeMillis()
        var randomRow = Random.nextInt(0, mRcCount)
        var blockCount = 0
        mBlockArray[randomRow].forEach { block ->
            if (block != null) blockCount++
        }
        while (blockCount == mRcCount) {
            randomRow = Random.nextInt(0, mRcCount)
            blockCount = 0
            mBlockArray[randomRow].forEach { block ->
                if (block != null) blockCount++
            }
        }

        var randomColumn = Random.nextInt(0, mRcCount)
        while (mBlockArray[randomRow][randomColumn] != null) {
            randomColumn = Random.nextInt(0, mRcCount)
        }
        Log.e("hsy1", "随机数时间 -> ${System.currentTimeMillis() - startTime}")
        Log.e("hsy1", "随机数 -> $randomRow    $randomColumn")
        return Pair(randomRow, randomColumn)
    }

    fun setScoreListener(listener: (Int) -> Unit) {
        mScoreListener = listener
    }

    fun setGameOverListener(listener: (Int) -> Unit) {
        mGameOverListener = listener
    }

    private inner class Game2048Background(context: Context) : View(context) {
        // 背景 paint
        private val mBackgroundPaint = Paint().apply {
            isAntiAlias = true
            color = colorBackground
        }

        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
            canvas.drawBackground()
        }

        /** 绘制背景 */
        private fun Canvas.drawBackground() {
            drawRoundRect(
                0f, 0f, width.toFloat(), height.toFloat(),
                mCornerRadius, mCornerRadius, mBackgroundPaint
            )

            val firstBlockSurfaceLeft = mGap
            val firstBlockSurfaceTop = mGap
            repeat(mRcCount) { row ->
                repeat(mRcCount) { column ->
                    drawRoundRect(
                        (firstBlockSurfaceLeft + column * (mBlockSize + mGap)).toFloat(),
                        (firstBlockSurfaceTop + row * (mBlockSize + mGap)).toFloat(),
                        (firstBlockSurfaceLeft + column * (mBlockSize + mGap) + mBlockSize).toFloat(),
                        (firstBlockSurfaceTop + row * (mBlockSize + mGap) + mBlockSize).toFloat(),
                        mCornerRadius, mCornerRadius, mBackgroundPaint
                    )
                }
            }
        }
    }

    inner class Game2048Block(
        context: Context,
        var row: Int, var column: Int,
    ) : View(context) {
        var value = 2
            set(value) {
                field = value
                mBlockPaint.color = mColorMap[value]!!
            }

        // block paint
        private val mBlockPaint = Paint().apply {
            isAntiAlias = true
        }

        // 数值 paint
        private val mValuePaint = TextPaint().apply {
            isAntiAlias = true
            color = Color.parseColor("#B8860B")
            textAlign = Paint.Align.CENTER
            typeface = mTypeFace ?: Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)
        }

        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
            canvas.apply {
                drawBlock()
                drawValue()
            }
        }

        // 绘制背景
        private fun Canvas.drawBlock() {
            drawRoundRect(
                0f, 0f, width.toFloat(), height.toFloat(),
                mCornerRadius, mCornerRadius, mBlockPaint
            )
        }

        // 绘制数值
        private fun Canvas.drawValue() {
            mValuePaint.textSize = mBlockSize / 3f
            val text = "$value"
            val textX = width / 2f
            val textY = (height - (mValuePaint.ascent() + mValuePaint.descent())) / 2
            drawText(text, textX, textY, mValuePaint)
        }
    }

    private fun View.animShow(onAnimEnd: () -> Unit = {}) {
        animate()
            .scaleX(0f)
            .scaleY(0f)
            .setDuration(0)
            .withEndAction {
                animate()
                    .scaleX(1f)
                    .scaleY(1f)
                    .setDuration(250)
                    .setInterpolator(DecelerateInterpolator())
                    .withEndAction { onAnimEnd() }
                    .start()
            }
            .start()
    }

    private fun View.animDismiss(onAnimEnd: () -> Unit = {}) {
        animate()
            .scaleX(0f)
            .scaleY(0f)
            .setDuration(10)
            .setInterpolator(DecelerateInterpolator())
            .withEndAction { onAnimEnd() }
            .start()
    }
}